#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
文件管理器

该模块提供了文件管理的核心功能，包括文件读写、文件类型检测、文件信息获取等。

创建时间: 2023-07-12
作者: Python Tools Team
"""

import os
import shutil
import tempfile
import hashlib
from typing import Optional, Union, Dict, Any, BinaryIO, TextIO, List, Tuple, Callable
from pathlib import Path
import mimetypes

from modules.logger import LogManager
from modules.exception import exception_handler, FileException


class FileManager:
    """
    文件管理器，提供统一的文件操作接口
    
    该类提供了文件读写、文件类型检测、文件信息获取等功能，支持多种文件类型。
    """
    
    def __init__(self, logger=None):
        """
        初始化文件管理器
        
        Args:
            logger: 日志记录器，如果为None则使用默认日志记录器
        """
        self.logger = logger or LogManager.get_both_logger(name="file_manager")
        # 初始化MIME类型检测
        mimetypes.init()
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def read_text(self, file_path: Union[str, Path], encoding: str = 'utf-8') -> str:
        """
        读取文本文件内容
        
        Args:
            file_path: 文件路径
            encoding: 文件编码，默认为utf-8
        
        Returns:
            str: 文件内容
        
        Raises:
            FileException: 文件不存在或读取失败时抛出
        
        Examples:
            >>> file_manager = FileManager()
            >>> content = file_manager.read_text('example.txt')
            >>> print(content)
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        with open(file_path, 'r', encoding=encoding) as f:
            content = f.read()
        
        self.logger.debug(f"已读取文本文件: {file_path}")
        return content
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def write_text(self, file_path: Union[str, Path], content: str, encoding: str = 'utf-8', 
                  append: bool = False, create_dirs: bool = True) -> bool:
        """
        写入文本到文件
        
        Args:
            file_path: 文件路径
            content: 要写入的内容
            encoding: 文件编码，默认为utf-8
            append: 是否追加模式，默认为False（覆盖模式）
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 写入成功返回True
        
        Raises:
            FileException: 写入失败时抛出
        
        Examples:
            >>> file_manager = FileManager()
            >>> file_manager.write_text('example.txt', 'Hello, World!')
        """
        file_path = Path(file_path)
        
        # 如果需要，创建父目录
        if create_dirs:
            file_path.parent.mkdir(parents=True, exist_ok=True)
        
        mode = 'a' if append else 'w'
        with open(file_path, mode, encoding=encoding) as f:
            f.write(content)
        
        self.logger.debug(f"已{'追加' if append else '写入'}文本到文件: {file_path}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def read_binary(self, file_path: Union[str, Path]) -> bytes:
        """
        读取二进制文件内容
        
        Args:
            file_path: 文件路径
        
        Returns:
            bytes: 文件内容
        
        Raises:
            FileException: 文件不存在或读取失败时抛出
        
        Examples:
            >>> file_manager = FileManager()
            >>> data = file_manager.read_binary('example.bin')
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        with open(file_path, 'rb') as f:
            content = f.read()
        
        self.logger.debug(f"已读取二进制文件: {file_path}")
        return content
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def write_binary(self, file_path: Union[str, Path], content: bytes, 
                    append: bool = False, create_dirs: bool = True) -> bool:
        """
        写入二进制数据到文件
        
        Args:
            file_path: 文件路径
            content: 要写入的二进制数据
            append: 是否追加模式，默认为False（覆盖模式）
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 写入成功返回True
        
        Raises:
            FileException: 写入失败时抛出
        
        Examples:
            >>> file_manager = FileManager()
            >>> file_manager.write_binary('example.bin', b'\x00\x01\x02\x03')
        """
        file_path = Path(file_path)
        
        # 如果需要，创建父目录
        if create_dirs:
            file_path.parent.mkdir(parents=True, exist_ok=True)
        
        mode = 'ab' if append else 'wb'
        with open(file_path, mode) as f:
            f.write(content)
        
        self.logger.debug(f"已{'追加' if append else '写入'}二进制数据到文件: {file_path}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def read_lines(self, file_path: Union[str, Path], encoding: str = 'utf-8', 
                  strip: bool = True) -> List[str]:
        """
        按行读取文本文件内容
        
        Args:
            file_path: 文件路径
            encoding: 文件编码，默认为utf-8
            strip: 是否去除每行首尾的空白字符，默认为True
        
        Returns:
            List[str]: 文件内容的行列表
        
        Raises:
            FileException: 文件不存在或读取失败时抛出
        
        Examples:
            >>> file_manager = FileManager()
            >>> lines = file_manager.read_lines('example.txt')
            >>> for line in lines:
            >>>     print(line)
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        with open(file_path, 'r', encoding=encoding) as f:
            if strip:
                lines = [line.strip() for line in f]
            else:
                lines = [line.rstrip('\n') for line in f]
        
        self.logger.debug(f"已按行读取文本文件: {file_path}")
        return lines
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def write_lines(self, file_path: Union[str, Path], lines: List[str], 
                   encoding: str = 'utf-8', append: bool = False, 
                   create_dirs: bool = True) -> bool:
        """
        按行写入文本到文件
        
        Args:
            file_path: 文件路径
            lines: 要写入的行列表
            encoding: 文件编码，默认为utf-8
            append: 是否追加模式，默认为False（覆盖模式）
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 写入成功返回True
        
        Raises:
            FileException: 写入失败时抛出
        
        Examples:
            >>> file_manager = FileManager()
            >>> lines = ['Line 1', 'Line 2', 'Line 3']
            >>> file_manager.write_lines('example.txt', lines)
        """
        file_path = Path(file_path)
        
        # 如果需要，创建父目录
        if create_dirs:
            file_path.parent.mkdir(parents=True, exist_ok=True)
        
        mode = 'a' if append else 'w'
        with open(file_path, mode, encoding=encoding) as f:
            for line in lines:
                f.write(f"{line}\n")
        
        self.logger.debug(f"已{'追加' if append else '写入'}行到文件: {file_path}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def copy(self, source: Union[str, Path], destination: Union[str, Path], 
             create_dirs: bool = True) -> bool:
        """
        复制文件
        
        Args:
            source: 源文件路径
            destination: 目标文件路径
            create_dirs: 如果目标父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 复制成功返回True
        
        Raises:
            FileException: 源文件不存在或复制失败时抛出
        
        Examples:
            >>> file_manager = FileManager()
            >>> file_manager.copy('source.txt', 'destination.txt')
        """
        source = Path(source)
        destination = Path(destination)
        
        if not source.exists():
            raise FileException(f"源文件不存在: {source}")
        
        # 如果需要，创建目标父目录
        if create_dirs:
            destination.parent.mkdir(parents=True, exist_ok=True)
        
        shutil.copy2(source, destination)
        
        self.logger.debug(f"已复制文件: {source} -> {destination}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def move(self, source: Union[str, Path], destination: Union[str, Path], 
             create_dirs: bool = True) -> bool:
        """
        移动文件
        
        Args:
            source: 源文件路径
            destination: 目标文件路径
            create_dirs: 如果目标父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 移动成功返回True
        
        Raises:
            FileException: 源文件不存在或移动失败时抛出
        
        Examples:
            >>> file_manager = FileManager()
            >>> file_manager.move('source.txt', 'destination.txt')
        """
        source = Path(source)
        destination = Path(destination)
        
        if not source.exists():
            raise FileException(f"源文件不存在: {source}")
        
        # 如果需要，创建目标父目录
        if create_dirs:
            destination.parent.mkdir(parents=True, exist_ok=True)
        
        shutil.move(str(source), str(destination))
        
        self.logger.debug(f"已移动文件: {source} -> {destination}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def delete(self, file_path: Union[str, Path]) -> bool:
        """
        删除文件
        
        Args:
            file_path: 文件路径
        
        Returns:
            bool: 删除成功或文件不存在返回True
        
        Raises:
            FileException: 删除失败时抛出
        
        Examples:
            >>> file_manager = FileManager()
            >>> file_manager.delete('example.txt')
        """
        file_path = Path(file_path)
        
        if not file_path.exists():
            self.logger.warning(f"要删除的文件不存在: {file_path}")
            return True
        
        if file_path.is_dir():
            raise FileException(f"路径是目录而非文件: {file_path}")
        
        file_path.unlink()
        
        self.logger.debug(f"已删除文件: {file_path}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def get_file_info(self, file_path: Union[str, Path]) -> Dict[str, Any]:
        """
        获取文件信息
        
        Args:
            file_path: 文件路径
        
        Returns:
            Dict[str, Any]: 包含文件信息的字典，包括大小、修改时间、创建时间、访问时间、MIME类型等
        
        Raises:
            FileException: 文件不存在时抛出
        
        Examples:
            >>> file_manager = FileManager()
            >>> info = file_manager.get_file_info('example.txt')
            >>> print(f"文件大小: {info['size']} 字节")
            >>> print(f"修改时间: {info['modified_time']}")
        """
        file_path = Path(file_path)
        
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        stat = file_path.stat()
        mime_type, encoding = mimetypes.guess_type(str(file_path))
        
        info = {
            'name': file_path.name,
            'path': str(file_path.absolute()),
            'size': stat.st_size,
            'modified_time': stat.st_mtime,
            'created_time': stat.st_ctime,
            'accessed_time': stat.st_atime,
            'is_dir': file_path.is_dir(),
            'mime_type': mime_type or 'application/octet-stream',
            'encoding': encoding,
            'extension': file_path.suffix.lower() if file_path.suffix else '',
        }
        
        return info
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def get_file_hash(self, file_path: Union[str, Path], algorithm: str = 'md5', 
                     chunk_size: int = 8192) -> str:
        """
        计算文件哈希值
        
        Args:
            file_path: 文件路径
            algorithm: 哈希算法，可选值：md5, sha1, sha256, sha512等
            chunk_size: 读取文件的块大小，默认为8KB
        
        Returns:
            str: 文件的哈希值（十六进制字符串）
        
        Raises:
            FileException: 文件不存在或计算失败时抛出
            ValueError: 不支持的哈希算法
        
        Examples:
            >>> file_manager = FileManager()
            >>> md5 = file_manager.get_file_hash('example.txt', 'md5')
            >>> print(f"MD5: {md5}")
            >>> sha256 = file_manager.get_file_hash('example.txt', 'sha256')
            >>> print(f"SHA256: {sha256}")
        """
        file_path = Path(file_path)
        
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        try:
            hash_obj = hashlib.new(algorithm)
        except ValueError:
            raise ValueError(f"不支持的哈希算法: {algorithm}")
        
        with open(file_path, 'rb') as f:
            while True:
                data = f.read(chunk_size)
                if not data:
                    break
                hash_obj.update(data)
        
        return hash_obj.hexdigest()
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def create_temp_file(self, prefix: Optional[str] = None, suffix: Optional[str] = None, 
                        dir: Optional[Union[str, Path]] = None, text: bool = True) -> Tuple[TextIO, str]:
        """
        创建临时文件
        
        Args:
            prefix: 临时文件名前缀
            suffix: 临时文件名后缀
            dir: 临时文件所在目录
            text: 是否以文本模式打开，默认为True
        
        Returns:
            Tuple[TextIO, str]: 临时文件对象和文件路径
        
        Raises:
            FileException: 创建失败时抛出
        
        Examples:
            >>> file_manager = FileManager()
            >>> temp_file, temp_path = file_manager.create_temp_file(prefix='test_', suffix='.txt')
            >>> temp_file.write('Hello, World!')
            >>> temp_file.close()
            >>> print(f"临时文件路径: {temp_path}")
        """
        if dir is not None:
            dir = str(dir)
        
        temp_file, temp_path = tempfile.mkstemp(prefix=prefix, suffix=suffix, dir=dir, text=text)
        
        # 将文件描述符转换为文件对象
        mode = 'w+' if text else 'wb+'
        file_obj = os.fdopen(temp_file, mode)
        
        self.logger.debug(f"已创建临时文件: {temp_path}")
        return file_obj, temp_path
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def create_temp_dir(self, prefix: Optional[str] = None, suffix: Optional[str] = None, 
                       dir: Optional[Union[str, Path]] = None) -> str:
        """
        创建临时目录
        
        Args:
            prefix: 临时目录名前缀
            suffix: 临时目录名后缀
            dir: 临时目录所在的父目录
        
        Returns:
            str: 临时目录路径
        
        Raises:
            FileException: 创建失败时抛出
        
        Examples:
            >>> file_manager = FileManager()
            >>> temp_dir = file_manager.create_temp_dir(prefix='test_')
            >>> print(f"临时目录路径: {temp_dir}")
        """
        if dir is not None:
            dir = str(dir)
        
        temp_dir = tempfile.mkdtemp(prefix=prefix, suffix=suffix, dir=dir)
        
        self.logger.debug(f"已创建临时目录: {temp_dir}")
        return temp_dir
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def is_file_exists(self, file_path: Union[str, Path]) -> bool:
        """
        检查文件是否存在
        
        Args:
            file_path: 文件路径
        
        Returns:
            bool: 文件存在且是文件（非目录）返回True，否则返回False
        
        Examples:
            >>> file_manager = FileManager()
            >>> if file_manager.is_file_exists('example.txt'):
            >>>     print('文件存在')
            >>> else:
            >>>     print('文件不存在')
        """
        file_path = Path(file_path)
        return file_path.exists() and file_path.is_file()
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def is_dir_exists(self, dir_path: Union[str, Path]) -> bool:
        """
        检查目录是否存在
        
        Args:
            dir_path: 目录路径
        
        Returns:
            bool: 目录存在且是目录返回True，否则返回False
        
        Examples:
            >>> file_manager = FileManager()
            >>> if file_manager.is_dir_exists('example_dir'):
            >>>     print('目录存在')
            >>> else:
            >>>     print('目录不存在')
        """
        dir_path = Path(dir_path)
        return dir_path.exists() and dir_path.is_dir()
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def get_file_size(self, file_path: Union[str, Path], human_readable: bool = False) -> Union[int, str]:
        """
        获取文件大小
        
        Args:
            file_path: 文件路径
            human_readable: 是否返回人类可读的大小字符串，默认为False
        
        Returns:
            Union[int, str]: 文件大小（字节数）或人类可读的大小字符串
        
        Raises:
            FileException: 文件不存在时抛出
        
        Examples:
            >>> file_manager = FileManager()
            >>> size = file_manager.get_file_size('example.txt')
            >>> print(f"文件大小: {size} 字节")
            >>> readable_size = file_manager.get_file_size('example.txt', human_readable=True)
            >>> print(f"文件大小: {readable_size}")
        """
        file_path = Path(file_path)
        
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        if not file_path.is_file():
            raise FileException(f"路径不是文件: {file_path}")
        
        size = file_path.stat().st_size
        
        if not human_readable:
            return size
        
        # 转换为人类可读的大小
        for unit in ['B', 'KB', 'MB', 'GB', 'TB', 'PB']:
            if size < 1024 or unit == 'PB':
                break
            size /= 1024
        
        return f"{size:.2f} {unit}"
    
    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        # 没有需要清理的资源
        pass